#include "stdafx.hpp"
#include "ColligereDatabaseTable.hpp"
#include "ColligereUtils.hpp"

//////////////////////////////////////////////////////////
// ColligereDatabaseTable
//////////////////////////////////////////////////////////

col::Column col::DatabaseTable::msInvalidColumn;

col::DatabaseTable::DatabaseTable() 
{
}

col::DatabaseTable::DatabaseTable ( const wxString &rTableName, 
								   wxUint32 ownerDatabaseTypeId, 
								   const wxString &rParentTableName, 
								   wxUint32 autoColSettings )
{
	Create ( rTableName, ownerDatabaseTypeId, rParentTableName, autoColSettings );

	/*
	wxLogDebug ( "DatabaseTable ( name = '%s', ownerDatabaseTypeId = %d, "\
		"itemName = '%s', parentName = '%s' )", rTableName.c_str(),
		ownerDatabaseTypeId, rItemName.c_str(), rParentTableName.c_str() );
	*/
}

void col::DatabaseTable::Create ( const wxString &rName, 
								 wxUint32 ownerDatabaseTypeId, 
								 const wxString &rParentTableName,
								 wxUint32 autoColSettings )
{
	wxASSERT ( !rName.empty() );

	mAutoColSettings = autoColSettings;
	mName = rName;
	mOwnerDatabaseTypeId = ownerDatabaseTypeId;
	mParentTableName = rParentTableName;

	if ( mAutoColSettings & AutoColumns::Id )
	{
		RegisterColumn ( Column ( 0, "id", Column::Type::Integer, 
			Column::Constraint::PrimaryKey ) );
	}
}

col::DatabaseTable &col::DatabaseTable::operator += ( const Column &rCol )
{
	return RegisterColumn ( rCol );
}

col::DatabaseTable &col::DatabaseTable::RegisterColumn ( const Column &rCol )
{
#ifdef __WXDEBUG__
	if ( rCol.GetIndex() == 0 )
	{
		wxASSERT_MSG ( rCol.HasConstraint ( Column::Constraint::PrimaryKey ),
			"The index 0 is reserved for the ID column!" );
	}
	ColumnSet::const_iterator it = mColumns.find ( rCol );
	wxASSERT_MSG ( it == mColumns.end(), "Column already registered!" );
#endif __WXDEBUG__
	mColumns.insert ( rCol );
	return *this;
}

void col::DatabaseTable::Finalize() 
{
	typedef AutoColumns ac;
	typedef Column c;

	if ( mAutoColSettings & ac::ParentId )
	{
		*this += c ( GetColumnCount(), "parent_id", c::Type::Integer ); 
	}
	if ( mAutoColSettings & ac::TimestampAdded )
	{
		*this += c ( GetColumnCount(), "timestamp_added", c::Type::Text ); 
	}
	if ( mAutoColSettings & ac::TimestampUpdated )
	{
		*this += c ( GetColumnCount(), "timestamp_updated", c::Type::Text ); 
	}
	if ( mAutoColSettings & ac::Trashed )
	{
		*this += c ( GetColumnCount(), "is_trashed", c::Type::Integer ); 
	}
}

col::DatabaseTable &col::DatabaseTable::operator += ( const Trigger &rTr )
{
	return RegisterTrigger ( rTr );
}

col::DatabaseTable &col::DatabaseTable::RegisterTrigger ( const Trigger &rTr )
{
	TriggerDeque::const_iterator it = std::find ( mTriggers.begin(),
		mTriggers.end(), rTr );

	wxASSERT_MSG ( it == mTriggers.end(), "Trigger already registered!" );

	mTriggers.push_back ( rTr );

	return *this;
}

const wxString &col::DatabaseTable::GetColumnNameByIndex ( wxUint32 index ) const
{
	wxASSERT ( mColumns.size() > 0 && index < mColumns.size() );

	ColumnSet::const_iterator it = mColumns.find ( Column ( index ) );

	return it != mColumns.end() ? it->GetName() : empty_string;
}

wxInt32 col::DatabaseTable::GetColumnIndexByName ( const wxString &rName ) const
{
	wxASSERT_MSG ( mColumns.size() > 0, "No columns registered!" );

	ColumnSet::const_iterator it = std::find ( mColumns.begin(), mColumns.end(), rName );

	return it != mColumns.end() ? it->GetIndex() : wxNOT_FOUND;
}

const wxString col::DatabaseTable::ToSqlSelectAllQuery ( std::deque < wxUint32 > colOrderBy ) const
{
	using namespace boost;
	using namespace std;

	deque < wxUint32 > order_by = 
		colOrderBy.empty() ? mDefaultOrderBy : colOrderBy;

	map < wxUint32, wxString > order_by_map;

	wxString order_by_cols;

	for ( wxUint32 idx = 0; idx < order_by.size(); ++idx )
	{
		append_if ( order_by_cols, ", ", not1 ( is_empty() ) ); 
		order_by_cols += GetColumnNameByIndex ( order_by[idx] );
	}
	if ( !order_by_cols.empty() )
	{
		return str ( format ( "SELECT * FROM %1% ORDER BY %2%;") %
			GetName() % order_by_cols );
	}
	return "SELECT * FROM " + GetName() + ";";
}

void col::DatabaseTable::SetDefaultOrderBy ( const std::deque < wxUint32 > &rColOrder )
{
	mDefaultOrderBy = rColOrder;
}

const wxString col::DatabaseTable::ToSqlSelectAllQuery ( wxUint32 parentDbId,
														std::deque < wxUint32 > colOrderBy ) const
{
	using namespace boost;
	using namespace std;

	wxASSERT ( parentDbId != 0 );

	deque < wxUint32 > order_by = 
		colOrderBy.empty() ? mDefaultOrderBy : colOrderBy;

	wxString order_by_cols;

	for ( wxUint32 idx = 0; idx < order_by.size(); ++idx )
	{
		append_if ( order_by_cols, ", ", not1 ( is_empty() ) ); 
		order_by_cols += GetColumnNameByIndex ( order_by[idx] );
	}
	if ( !order_by_cols.empty() )
	{
		return str ( format ( "SELECT * FROM %1% WHERE %2% = %3% "\
			"ORDER BY %4%;") % GetName() % GetParentIdColumn().GetName() % 
			parentDbId % order_by_cols );
	}
	return str ( format ( "SELECT * FROM %1% WHERE %2% = %3%;" ) % 
		GetName() % GetParentIdColumn().GetName() % parentDbId );			
}

const wxString col::DatabaseTable::ToSqlCreateTableQuery ( bool includeTriggers )
{
	using namespace boost;
	using namespace std;

	wxASSERT ( mColumns.size() > 0 );

	wxString cols;

	foreach ( const Column &c, mColumns )
	{
		append_if ( cols, ", ", not1 ( is_empty() ) ); 
		cols += c.ToString();
	}

	const wxString tables ( str ( format ( "CREATE TABLE IF NOT EXISTS %1% "\
		"( %2% );") % mName % cols ) );

	if ( includeTriggers && mTriggers.size() > 0 )
	{
		wxString triggers;

		foreach ( Trigger &t, mTriggers )
		{
			triggers += t.ToSqlCreateTriggerQuery();
		}
		return tables + triggers;
	}
	return tables;
}

const col::Column &col::DatabaseTable::GetColumn ( wxUint32 index ) const
{
	ColumnSet::const_iterator it = std::find ( mColumns.begin(), mColumns.end(), index );

	if ( it != mColumns.end() )
	{
		return *it;
	}
	return msInvalidColumn;
}

col::Column &col::DatabaseTable::GetColumn ( wxUint32 index )
{
	ColumnSet::iterator it = std::find ( mColumns.begin(), mColumns.end(), index );

	if ( it != mColumns.end() )
	{
#ifdef __WXMSW__
		return (*it);
#else
		return const_cast < Column & > ( *it );
#endif
	}
	return msInvalidColumn;
}

const col::Column &col::DatabaseTable::GetIdColumn() const
{
	wxASSERT ( mColumns.size() > 0 );
	return *mColumns.begin();
}

bool col::DatabaseTable::HasParentIdColumn() const
{
	wxASSERT ( mColumns.size() > 0 );
	return FindColumn ( "parent_id" ) != 0;
}

const col::Column &col::DatabaseTable::GetParentIdColumn() const
{
	wxASSERT ( mColumns.size() > 0 );
	wxASSERT ( !mParentTableName.empty() );

	const Column *pc = FindColumn ( "parent_id" );
	wxASSERT ( pc );
	return pc ? *pc : msInvalidColumn;
}

bool col::DatabaseTable::operator == ( const DatabaseTable &rCmp ) const 
{
	return mName == rCmp.mName && mOwnerDatabaseTypeId == 
		rCmp.mOwnerDatabaseTypeId;
}

const wxString &col::DatabaseTable::GetName() const 
{ 
	return mName; 
}

const wxString &col::DatabaseTable::GetParentName() const 
{ 
	return mParentTableName; 
}

const wxString col::DatabaseTable::GetItemName() const 
{ 
	return mName.substr ( 0, mName.length() - 1 );
}

size_t col::DatabaseTable::GetColumnCount() const 
{ 
	return mColumns.size(); 
}

size_t col::DatabaseTable::GetTriggerCount() const 
{ 
	return mTriggers.size(); 
}

const col::ColumnSet &col::DatabaseTable::GetColumns() const 
{ 
	return mColumns; 
}

wxUint32 col::DatabaseTable::GetOwnerDatabaseTypeId() const 
{ 
	return mOwnerDatabaseTypeId; 
}

const col::Column *col::DatabaseTable::FindColumn
( const wxString &rName ) const
{
	ColumnSet::const_iterator it = std::find ( mColumns.begin(),
		mColumns.end(), rName );
	return it != mColumns.end() ? &(*it) : 0;
}

/////////////////////////////////////////////////
// Column
/////////////////////////////////////////////////

col::Column::Column() : mIndx ( -1 ), mType ( -1 ), 
mConstraints ( -1 )
{
}

col::Column::Column ( wxUint32 indx, const wxString &rName, wxUint32 type, 
					 wxUint32 constraints ) : mIndx ( indx ), mType ( type ), 
					 mConstraints ( constraints ), mName ( rName )
{
	wxASSERT ( !mName.empty() );
	wxASSERT ( mType == Column::Type::Integer || mType == Column::Type::Text );
}

bool col::Column::operator < ( const Column &rCol ) const 
{ 
	return mIndx < rCol.mIndx; 
}

bool col::Column::operator == ( wxInt32 colIndex ) const 
{ 
	return mIndx == colIndex; 
}

bool col::Column::operator == ( const wxString &rName ) const 
{
	return mName == rName; 
}

bool col::Column::operator == ( const Column &rCol ) const 
{ 
	return ( mIndx == rCol.mIndx && mType == rCol.mType && mName == rCol.mName ); 
}

const wxString &col::Column::GetName() const 
{ 
	return mName; 
}

wxInt32 col::Column::GetConstraints() const 
{ 
	return mConstraints; 
}

wxInt32 col::Column::GetType() const 
{ 
	return mType; 
}

wxInt32 col::Column::GetIndex() const 
{ 
	return mIndx; 
}

const col::ControlLink &col::Column::GetCtrlLink() const
{
	return mCtrlLink;
}

bool col::Column::IsIdColumn() const 
{ 
	return ( mIndx == 0 && mConstraints & Constraint::PrimaryKey );
}

bool col::Column::IsInternalColumn() const
{
	return ( IsTimestampColumn() || IsParentIdColumn() ||
		IsIdColumn() || mName == "is_trashed" );
}

bool col::Column::IsTimestampColumn() const
{
	return mName.StartsWith ( "timestamp" );
}

bool col::Column::IsParentIdColumn() const 
{ 
	return mName == "parent_id";
}

bool col::Column::HasConstraint ( wxUint32 constraint ) const
{
	return ( mConstraints & constraint ) != 0;
}

const wxString col::Column::FormatTypeToString() const
{
	return ( mType == Type::Integer ? "INTEGER" : "TEXT" );
}

const wxString col::Column::FormatConstraintsToString() const
{
	using namespace boost;
	using namespace std;

	typedef Column col;

	wxString ret;

	if ( HasConstraint ( Constraint::Unique ) )
	{
		ret += "UNIQUE";
	}
	if ( HasConstraint ( Constraint::NotNull ) )
	{
		ret += ret.empty() ? "NOT NULL" : " NOT NULL";
	}
	if ( HasConstraint ( Constraint::PrimaryKey ) )
	{
		ret += ret.empty() ? "PRIMARY KEY" : " PRIMARY KEY";
	}
	if ( HasConstraint ( Constraint::AutoIncrement ) )
	{
		ret += ret.empty() ? "AUTOINCREMENT" : " AUTOINCREMENT";
	}
	return ret;
}

const wxString col::Column::ToString() const
{
	using namespace boost;
	using namespace std;

	const wxString type ( FormatTypeToString() );
	const wxString constraints ( FormatConstraintsToString() );

	if ( !constraints.empty() )
	{
		return str ( format ( "%1% %2% %3%" ) % mName % type % constraints );
	}
	return str ( format ( "%1% %2%" ) % mName % type );	
}

bool col::Column::IsInvalid() const
{
	return mIndx == -1;
}

/////////////////////////////////////////////////
// Trigger
/////////////////////////////////////////////////

col::Trigger::Trigger ( const wxString &rName, wxUint32 eventType, 
					   wxUint32 execPosition,  const wxString &rTriggerSteps, 
					   const wxString &rParentTable ) : mName ( rName ), 
					   mEventType ( eventType ),mExecPosition ( execPosition ),
					   mTriggerSteps ( rTriggerSteps ), 
					   mParentTableName ( rParentTable )
{
}

const wxString col::Trigger::ToSqlCreateTriggerQuery()
{
	using namespace boost;
	using namespace std;

	const wxString exec_pos ( mExecPosition == ExecPosition::After ? "AFTER" : "BEFORE" );

	wxString db_event;

	if ( mEventType == EventType::Delete ) 
	{ 
		db_event = "DELETE"; 
	}
	else if ( mEventType == EventType::Insert ) 
	{ 
		db_event = "INSERT"; 
	}
	else if ( mEventType == EventType::Update ) 
	{ 
		db_event = "UPDATE"; 
	}
	else 
	{ 
		wxFAIL_MSG ( "Invalid event type flag!" ); 
	}

	const wxString action ( "FOR EACH ROW" );

	return str ( format ( "CREATE TRIGGER IF NOT EXISTS %1% %2% %3% ON "\
		"%4% %5% BEGIN %6% END;" ) % mName % exec_pos % db_event %
		mParentTableName % action % mTriggerSteps );
}

const wxString &col::Trigger::GetName() const 
{ 
	return mName; 
}

const wxString &col::Trigger::GetParentTableName() const 
{ 
	return mParentTableName; 
}

const wxString &col::Trigger::GetTriggerSteps() const 
{ 
	return mTriggerSteps; 
}

wxUint32 col::Trigger::GetEventType() const 
{ 
	return mEventType; 
}

wxUint32 col::Trigger::GetExecPosition() const 
{ 
	return mExecPosition; 
}

bool col::Trigger::operator == ( const Trigger &rCmp ) const
{ 
	return mName == rCmp.mName; 
}

/////////////////////////////////////////////////
// ColligereDatabaseTableListHandler
/////////////////////////////////////////////////

void col::DatabaseTableListHandler::RegisterTable 
( const DatabaseTable &rTable )
{
	DatabaseTableDeque::const_iterator it = std::find
		( mTableList.begin(), mTableList.end(), rTable );

	wxASSERT_MSG ( it == mTableList.end(), "The table is already registered!" );

	if ( it == mTableList.end() )
	{
		wxLogDebug ( "Registering the table \"%s\" to the global database table "\
			"list.", rTable.GetName() );
		mTableList.push_back ( rTable );
	}
}

bool col::DatabaseTableListHandler::GetTable ( wxUint32 dbType, 
											  const wxString &rName, 
											  DatabaseTable &rTo ) const
{
	DatabaseTableDeque::const_iterator it = std::find ( mTableList.begin(), 
		mTableList.end(), DatabaseTable ( rName, dbType, empty_string ) ); 

	if ( it != mTableList.end() )
	{
		rTo = *it;
		return true;
	}
	return false;
}

col::DatabaseTable *col::DatabaseTableListHandler::GetTable 
( wxUint32 dbType, const wxString &rName )
{
	DatabaseTableDeque::iterator it = std::find ( mTableList.begin(), 
		mTableList.end(), DatabaseTable ( rName, dbType, empty_string ) ); 
	if ( it != mTableList.end() )
	{
		return &(*it);
	}
	return 0;
}

const col::DatabaseTableDeque 
col::DatabaseTableListHandler::GetTableList ( wxUint32 dbType ) const
{
	DatabaseTableDeque tables;

	DatabaseTableDeque::const_iterator it = mTableList.begin();

	foreach ( const DatabaseTable &t, mTableList )
	{
		if ( t.GetOwnerDatabaseTypeId() == dbType )
		{
			tables.push_back(t);
		}
	}
	return tables;
}
